﻿using System;

using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;

namespace GameFramework
{
    public static class CollectionPool<T1, T2> where T1 : ICollection<T2>, new()
    {
        private static ObjectPool<T1> objectPool = new ObjectPool<T1>(null, (a) => a.Clear());
        public static T1 Acquire()
        {
            return objectPool.Get();
        }
        public static void Release(T1 t)
        {
            objectPool.Release(t);
        }
    }
    public static class CollectionPool<T1, T2, T3> where T1 : IDictionary<T2, T3>, new()
    {
        private static ObjectPool<T1> objectPool = new ObjectPool<T1>(null, (a) => a.Clear());
        public static T1 Acquire()
        {
            return objectPool.Get();
        }
        public static void Release(T1 t)
        {
            objectPool.Release(t);
        }
    }
    /// <summary>
    /// 泛型的IRerennce池
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public static class GenericReferencePool<T> where T : IReference, new()
    {
        private static ObjectPool<T> objectPool = new ObjectPool<T>(null, (a) => a.Clear());
        public static T Acquire()
        {
            return objectPool.Get();
        }
        public static void Release(T t)
        {
            objectPool.Release(t);
        }
    }
#if UNITY_EDITOR
    public static class ObjectPoolDebug
    {
        internal static void Add<T>(ObjectPool<T> objectPool)
        {
            HashSet.Add(objectPool);
        }

        internal static void Remove<T>(ObjectPool<T> objectPool)
        {
            HashSet.Remove(objectPool);
        }
        static ObjectPoolDebug()
        {
            HashSet = new HashSet<IBaseObjectPool>();
        }
        public static HashSet<IBaseObjectPool> HashSet { get; private set; }
    }
#endif
    public interface IBaseObjectPool
    {
        string Name { get; }
        int countAll { get; }
        int countActive { get; }
        int countInactive { get; }
    }

    public class ObjectPool<T> : IBaseObjectPool
    {

        public ObjectPool(Action<T> actionOnGet, Action<T> actionOnRelease)
        {
#if UNITY_EDITOR
            var type = typeof(T);
            if (type.IsGenericType)
            {
                StringBuilder sb = new StringBuilder();
                var ts = type.GenericTypeArguments;
                sb.Append(type.Name);
                foreach (var item in ts)
                {
                    sb.Append("_");
                    sb.Append(item.Name);
                }
                Name = sb.ToString();
            }
            else
            {
                Name = type.Name;
            }

            ObjectPoolDebug.Add(this);
#endif
            this.m_ActionOnGet = actionOnGet;
            this.m_ActionOnRelease = actionOnRelease;
        }
#if UNITY_EDITOR
        ~ObjectPool()
        {
            ObjectPoolDebug.Remove(this);
        }
#endif
        private readonly Stack<T> m_Stack = new Stack<T>();

        private readonly Action<T> m_ActionOnGet;

        private readonly Action<T> m_ActionOnRelease;

        public string Name 
        { 
            get;
            private set;
        }

        public int countAll
        {
            get;
            private set;
        }

        public int countActive
        {
            get
            {
                return countAll - countInactive;
            }
        }

        public int countInactive
        {
            get
            {
                return m_Stack.Count;
            }
        }

        public T Get()
        {
            T t;
            lock (m_Stack)
            {
                if (m_Stack.Count == 0)
                {
                    t = Activator.CreateInstance<T>();
                    countAll++;
                }
                else
                {
                    t = m_Stack.Pop();
                }
                if (m_ActionOnGet != null)
                {
                    m_ActionOnGet(t);
                }
            }

            return t;
        }

        public void Release(T element)
        {
            lock (m_Stack)
            {
                if (m_Stack.Count > 0 && ReferenceEquals(m_Stack.Peek(), element))
                {
                    throw new GameFrameworkException("Internal error. Trying to destroy object that is already released to pool.");
                }
                if (m_ActionOnRelease != null)
                {
                    m_ActionOnRelease(element);
                }
                m_Stack.Push(element);
            }
        }

        public void Clear()
        {
            m_Stack.Clear();
        }
    }
}
